/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.io.InputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
public class Audio {
public static boolean enabled = true;
private static Thread player;
public static final AudioFormat fmt = new AudioFormat(44100, 16, 2, true, false);
private static Collection<CS> ncl = new LinkedList<CS>();
private static Object queuemon = new Object();
private static Collection<Runnable> queue = new LinkedList<Runnable>();
private static int bufsize = 32768;
public interface CS {
public boolean get(double[] sample);
}
public static class DataClip implements CS {
private InputStream clip;
private double vol, sp;
private int ack = 0;
private double[] ov = new double[2];
public boolean eof;
public DataClip(InputStream clip, double vol, double sp) {
this.clip = clip;
this.vol = vol;
this.sp = sp;
}
public DataClip(InputStream clip) {
this(clip, 1.0, 1.0);
}
public void finwait() throws InterruptedException {
synchronized (this) {
if (eof)
return;
wait();
}
}
public boolean get(double[] sm) {
try {
ack += 44100.0 * sp;
while (ack >= 44100) {
for (int i = 0; i < 2; i++) {
int b1 = clip.read();
int b2 = clip.read();
if ((b1 < 0) || (b2 < 0)) {
synchronized (this) {
eof = true;
notifyAll();
}
return (false);
}
int v = b1 + (b2 << 8);
if (v >= 32768)
v -= 65536;
ov[i] = ((double) v / 32768.0) * vol;
}
ack -= 44100;
}
} catch (java.io.IOException e) {
synchronized (this) {
eof = true;
notifyAll();
}
return (false);
}
for (int i = 0; i < 2; i++)
sm[i] = ov[i];
return (true);
}
}
private static class Player extends HackThread {
private Collection<CS> clips = new LinkedList<CS>();
private int srate, nch = 2;
Player() {
super("Haven audio player");
setDaemon(true);
srate = (int) fmt.getSampleRate();
}
private void fillbuf(byte[] buf, int off, int len) {
double[] val = new double[nch];
double[] sm = new double[nch];
while (len > 0) {
for (int i = 0; i < nch; i++)
val[i] = 0;
for (Iterator<CS> i = clips.iterator(); i.hasNext();) {
CS cs = i.next();
if (!cs.get(sm)) {
i.remove();
continue;
}
for (int ch = 0; ch < nch; ch++)
val[ch] += sm[ch];
}
for (int i = 0; i < nch; i++) {
int iv = (int) (val[i] * 32767.0);
if (iv < 0) {
if (iv < -32768)
iv = -32768;
iv += 65536;
} else {
if (iv > 32767)
iv = 32767;
}
buf[off++] = (byte) (iv & 0xff);
buf[off++] = (byte) ((iv & 0xff00) >> 8);
len -= 2;
}
}
}
public void run() {
SourceDataLine line = null;
try {
try {
line = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, fmt));
line.open(fmt, bufsize);
line.start();
} catch (Exception e) {
e.printStackTrace();
return;
}
byte[] buf = new byte[1024];
while (true) {
if (Thread.interrupted())
throw (new InterruptedException());
synchronized (queuemon) {
Collection<Runnable> queue = Audio.queue;
Audio.queue = new LinkedList<Runnable>();
for (Runnable r : queue)
r.run();
}
synchronized (ncl) {
for (CS cs : ncl)
clips.add(cs);
ncl.clear();
}
fillbuf(buf, 0, 1024);
for (int off = 0; off < buf.length; off += line.write(buf, off, buf.length - off))
;
}
} catch (InterruptedException e) {
} finally {
synchronized (Audio.class) {
player = null;
}
if (line != null)
line.close();
}
}
}
private static synchronized void ckpl() {
if (enabled) {
if (player == null) {
player = new Player();
player.start();
}
} else {
ncl.clear();
}
}
public static void play(CS clip) {
synchronized (ncl) {
ncl.add(clip);
}
ckpl();
}
public static void play(final InputStream clip, final double vol, final double sp) {
play(new DataClip(clip, vol, sp));
}
public static void play(byte[] clip, double vol, double sp) {
play(new DataClip(new java.io.ByteArrayInputStream(clip), vol, sp));
}
public static void play(byte[] clip) {
play(clip, Config.getSFXVolume(), 1.0);
}
public static void queue(Runnable d) {
synchronized (queuemon) {
queue.add(d);
}
ckpl();
}
private static void playres(Resource res) {
Collection<Resource.Audio> clips = res.layers(Resource.audio);
int s = (int) (Math.random() * clips.size());
Resource.Audio clip = null;
for (Resource.Audio cp : clips) {
clip = cp;
if (--s < 0)
break;
}
play(clip.clip);
}
public static void play(final Resource clip) {
queue(new Runnable() {
public void run() {
if (clip.loading)
queue.add(this);
else
playres(clip);
}
});
}
public static void play(final Indir<Resource> clip) {
queue(new Runnable() {
public void run() {
Resource r = clip.get();
if (r == null)
queue.add(this);
else
playres(r);
}
});
}
public static byte[] readclip(InputStream in) throws java.io.IOException {
AudioInputStream cs;
try {
cs = AudioSystem.getAudioInputStream(fmt, AudioSystem.getAudioInputStream(in));
} catch (UnsupportedAudioFileException e) {
throw (new java.io.IOException("Unsupported audio encoding"));
}
java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();
byte[] bbuf = new byte[65536];
while (true) {
int rv = cs.read(bbuf);
if (rv < 0)
break;
buf.write(bbuf, 0, rv);
}
return (buf.toByteArray());
}
public static void main(String[] args) throws Exception {
Collection<DataClip> clips = new LinkedList<DataClip>();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-b")) {
bufsize = Integer.parseInt(args[++i]);
} else {
DataClip c = new DataClip(new java.io.FileInputStream(args[i]));
clips.add(c);
}
}
for (DataClip c : clips)
play(c);
for (DataClip c : clips)
c.finwait();
}
static {
Console.setscmd("sfx", new Console.Command() {
public void run(Console cons, String[] args) {
play(Resource.load(args[1]));
}
});
}
}